/**
 * HTTP服务器:收到请求的处理过程
 * @param srv object 原生创建http.createServer实例
 * @param req object 请求实例 见:http.createServer
 * @param res object 响应实例 见:http.createServer
 * @param opt object|null 配置  $.srvHttp.create()
 * @return $.srvHttp
 */
$.srvHttp = function(srv, req, res, opt=null){
	let path='/'+req.url.replace(/[?#].*$/,'');
	const way=req.method;  path=$.path(path);
	const H = `# [\x1B[90m{date}\x1B[0m] {use}`
		+` \x1B[90m{acs}\x1B[0m ${way}:`+path;
	$.proc.call(this, {log:opt?._log,head:H});
	const $uri = $.router(path, opt?._router);
	const most=this.val(opt?._langMost||'_m');
	return Object.defineProperties(this, {
		$opt:{value:opt||{}}, $srv:{value:srv},
		$req:{value:req}, $res:{value:res},
		path:{value:path}, $route:{value:$uri},
		way:{value:way}, most:{value:most},
		__srvHttp:{value:{}},
	});
};
/**
 * HTTP服务器:创建  http/https/http2
 * @param opt json 配置  创建服务器
 * 		... mixed 创建服务器配置(NODE) + 额外:_*
 * 			若含pfx或key+cert:使用SSL, @前缀:文件
 * @param opt._http2 boolean 是否http2  默认:false
 * 		非加密http2, 可以创建,但无浏览支持
 * @param opt._log boolean 日志是否存文件 默认:true
 * @param opt._logRes boolean 日志是否记录响应数据
 * 		仅resJson时,  默认:false
 * @param opt._langForce array|null 强制指定语言
 * 		默认:false, true:忽略客户端接受语言
 * @param opt._langMost string|null 取得最优语言KEY
 * 		取得值: this.val(_langMost||'_m')
 * @param opt._langLast array|null 最终支持语言
 * 		详见: $.lang.able()的参数last
 * 		配置_langForce!=null时, 忽略配置
 * @param opt._htmMap json|null 响应HTML渲染函数映射
 *		若使用resHtml()响应正常数据,则必需
 *		_htmMap[ctr].call($ctr, data)
 *			ctr: $.router().ctr 控制器路径
 * @param opt._htmErr function|null 响应HTML异常渲染
 *		_htmErr.call($ctr|null, data+$err)
 * @param opt._router array|null 路由重写规则
 *		详见: $.router()参数rewrite
 * @param back function 回调  back(proc)
 * 		proc=$.srvHttp()实例, 每次请求新实例
 * @return Object 原生httpServer实例  重写:
 * 		listen(opt, add=false): Promise
 * 			opt: int|string|object 详见:NODE
 * 				IPC时:自动递归创建目录(*nix)
 * 			add: boolean 是否自增尝试可用端口
 * 				仅监听端口有效, 监听IPC无效
 */
$.srvHttp.create = function(opt, back){
	// 配置
	const SSL=opt['pfx']||(opt['key']&&opt['cert']);
	const HTTP = opt._http2 ? '2' : (SSL?'s':'');
	const H=require('node:http'+HTTP).createServer;
	SSL && ['pfx','key','cert'].forEach(k=>{//证书
		const v=opt[k];  if(!v||v[0]!=='@') return;
		opt[k] = $.fsRead(v.slice(1), {syn:true});
	});
	// 创建
	const eKey = '$.srvHttp.create.back';
	const eRes = '$.srvHttp.create.res500';
	const SRV = H(opt, async(req,res)=>{
		const $H = new $.srvHttp(SRV, req, res);
		try{ await back($H); }catch(e){
			e = $.err(eKey, e, true);
			if(res.writableEnded) return $H.log(e);
			try{ await $H.res500(e); }
			catch(e){ $H.log($.err(eRes,e,true)); }
		}
		await $H._end(); // 仍有异常->全局捕获
	});
	// 监听
	const RAW=SRV.listen, LISTEN=opt=>{
		return new Promise((Y,N)=>{
			const E=e=>N(e); SRV.on('error',E);
			 RAW.call(SRV,opt, ()=>{
				 SRV.off('error',E); Y(opt);
			 });
		});
	};
	const ADD = async opt=>{ // 端口自增尝试
		let v=0;  opt.port--;
		const W=()=>LISTEN(opt).catch(()=>v=0);
		while(!v){ v=++opt.port; await W(); }
		return v;
	};
	SRV.listen = function(opt, add=false){
		if((opt*1)===(opt*1)) opt = {port:opt};
		if(typeof opt==='string') opt={path:opt};
		if(!opt.path){ // 监听端口
			opt.port = opt.port || (SSL?443:80);
			return add ? ADD(opt) : LISTEN(opt);
		}
		if(process.platform==='win32'){
			opt.path='\\\\?\\'+opt.path;
			return LISTEN(opt);
		}
		return $.fsMkdir($.path(opt.path,1))
			.then(()=>LISTEN(opt));
	};
	return SRV;
};
$.srvHttp.qsDec = require('node:querystring').parse;
// 处理过程原型链
$.srvHttp.prototype=Object.create($.proc.prototype);
Object.assign($.srvHttp.prototype, {
	constructor: $.srvHttp,
	/**
	 * [内置]实例化定义(除$route=setRoute())
	 * @var $opt object 配置项 $.srvHttp.create().opt
	 * @var $srv object 原生创建http.createServer实例
	 * @var $req object 请求实例 见:http.createServer
	 * @var $res object 响应实例 见:http.createServer
	 * @var path string 请求路径 $.path(), 保留大小写
	 * @var way string 请求方法  GET, POST, ...
	 * @var most string|null 取得的客户端指定最优语言
	 * @var $route json 路由详情 this.setRoute()设置
	 */
	$opt:null, $srv:null, $req:null, $res:null,
	path:null, way:null, $route:null, most:null,

	/**
	 * 解析body数据(POST) TODO:暂不支持FormData
	 * @return Promise<Object>  {val:{},raw:''}
	 */
	parseBody(){
		const M = this.__srvHttp.parseBody = {val:{},raw:''};
		let rtn; this.parseBody=()=>rtn;//缓存重写parseBody()
		if(this.way!=='POST') return rtn = Promise.resolve(M);
		let T=(this.header('content_type')||'').split(';')[0];
		if(T.slice(0,20)==='multipart/form-data;')
			$.err('@$.srvHttp.600'); // 暂不支持formData格式
		let raw=''; this.$req.on('data',v=>raw+=v.toString());
		const [W,Y,N]=$.when(), END=()=>{
			M.raw=raw;  if(!raw) return; // 无内容:中断
			try{ // 解析: 捕获并忽略异常
				if(T==='application/x-www-form-urlencoded')
					M.val = $.srvHttp.qsDec(raw);
				else if(T==='application/json')
					M.val = JSON.parse(raw);
			}
			catch(e){}
		};
		const B=()=>{ try{ END(); Y(M); }catch(e){ N(e); } };
		this.$req.on('end',B);  return W;
	},

	/**
	 * 取得:请求路径后缀名
	 * @param dot boolean 返回是否包含'.'
	 * @return string 无后缀始终:'',无'.'
	 */
	ext(dot=false){
		let T = this.__srvHttp.ext;
		if(T===undefined){
			T=this.path.split('/').pop(); let n=T.indexOf('.')>0;
			T = this.__srvHttp.ext = n ? T.split('.').pop() : '';
		}
		return T ? (dot?'.':'')+T : '';
	},
	/**
	 * 取得:客户端IP
	 * @return string
	 */
	ipV4(){
		let T=this.__srvHttp.ipV4;  if(T) return T;
		T = this.$req.connection.remoteAddress;
		return this.__srvHttp.ipV4=T.split(':').pop();
	},
	/**
	 * 取得:请求头信息
	 * @param k string 取得KEY  null:全部
	 * 		统一: 小写, 字符'-'转为'_'
	 * @return Object|string|null
	 */
	header(k=null){
		let T = this.__srvHttp.header;
		if(!T){
			T=this.__srvHttp.header={};  const H=this.$req.headers;
			for(let k in H) T[k.toLowerCase().replace(/-/g,'_')]=H[k];
		}
		return k===null ? T : (T[k]??null);
	},
	/**
	 * 取得:请求cookie
	 * @param k string 取得KEY  null:全部
	 * @return Object|string|null
	 */
	cookie(k=null){
		let T = this.__srvHttp.cookie;
		if(!T){
			T=this.__srvHttp.cookie={}; const M=this.header('cookie');
			const SET = kv=>{ kv=kv.split('='); T[kv[0]]=kv[1]; };
			if(M) for(const kv of M.split(';')) SET(kv.trim());
		}
		return k===null ? T : (T[k]??null);
	},
	/**
	 * 取得:请求URL的QueryString参数
	 * @param k string|null 取得KEY  null:全部
	 * @return Object|mixed|null
	 */
	que(k=null){
		let T = this.__srvHttp.que;
		if(!T){
			let s=this.$req.url.split('?')[1];
			if((s=s?s.split('#')[0]:null))
				T = $.srvHttp.qsDec(s);
			T = this.__srvHttp.que = T || {};
		}
		return k===null ? T : (T[k]??null);
	},
	/**
	 * 取得:请求POST数据  parseBody()之后(无检测)
	 * @param k string|null 取得KEY null:全部
	 * @return Object|mixed|null
	 */
	post(k=null){
		if(this.way!=='POST') return null;
		let M = this.__srvHttp.parseBody.val;
		return k===null ? M : (M[k]??null);
	},
	/**
	 * 取得:原始body数据  parseBody()之后(无检测)
	 * @return string
	 */
	body(){ return this.__srvHttp.parseBody?.raw||''; },
	/**
	 * 取得:多来源参数  post>que>cookie>header
	 * @param k string 要取得的KEY
	 * @return mixed|null
	 */
	val(k){
		return this.post(k) ?? this.que(k) ??
			this.cookie(k) ?? this.header(k);
	},
	/**
	 * 取得:语言消息
	 * @param k string 语言编码
	 * @param vs json|null 语言模板参数
	 * @return string
	 */
	msg(k,vs=null){ return $.lang(k,vs,this.lang()); },
	/**
	 * 取得:接受的语言  $.lang.able()
	 * @return array
	 */
	lang(){
		if(this.$opt._langForce) return this.$opt._langForce;
		const T=this.__srvHttp, ABLE=[]; if(T.lang) return T.lang;
		const A=this.header('accept_language').replace(/-/g,'_');
		const ALLOW=A.replace(/\s+/g,'').toLowerCase().split(',');
		const PUSH = (k,v)=>{ v*=1; ABLE.push({k,v:v===v?v:1}); };
		for(const lang of ALLOW) PUSH(...lang.split(';q='));
		ABLE.sort((a,b)=>a.v>b.v?-1:1);  const a=ABLE.map(v=>v.k);
		return T.lang = $.lang.able(T.most,a,this.$opt._langLast);
	},

	/**
	 * 设置路由
	 * @param url string|null 路由路径 null:this.path
	 * @return object 详见:$.router()返回值
	 */
	setRoute(url=null){
		const u = $.router(url||this.path, this.$opt._router);
		Object.defineProperty(this,'$route',{value:u}); return u;
	},
	/**
	 * 设置:响应状态码  res*前调用
	 * @param v int 状态码
	 * @return this
	 */
	setCode(v){ this.$res.statusCode=v; return this; },
	/**
	 * 设置:响应MIME类型  res*前调用
	 * 		setHeader('Content-Type', ...)
	 * @param val string MIME类型值 或 后缀名(不含'.')
	 * @param ext string|null 方式及默认后缀  默认:null
	 * 		null:参数val为MIME类型值, 否则:默认后缀名
	 * @param enc string [后缀]文本类型时追加编码 '':不追加
	 * @return this
	 */
	setMime(val, ext=null, enc='utf-8'){
		if(ext!==null) val = $.mime(val,ext,enc);
		this.$res.setHeader('Content-Type', val);
		return this;
	},
	/**
	 * 设置:响应头  res*前调用
	 * @param mix json|string 设置数据
	 * 		string:key,  json:多个
	 * @param val string|null 值 mix=json:忽略
	 * @return this
	 */
	setHeader(mix, val=null){
		if(typeof mix!=='object') this.$res.setHeader(mix,val);
		else for(const k in mix) this.$res.setHeader(k,mix[k]);
		return this;
	},
	/**
	 * 设置:响应cookie  res*前调用
	 * @param key string cookie名  内部,无检测
	 * @param val string|null cookie值  null:移除 内部,无检测
	 * @param opt json|null 选项(配置)
	 * @param opt.pre string cookie名的前缀
	 * @param opt.exp int 有效期 int>0(秒) 0:session过期  默认:604800=7天
	 * @param opt.path string|null 路径  null:当前路径  默认:'/'
	 * @param opt.host string|null 域名(当前域名或其父级域名)  null:当前域名  注意:不带端口
	 * @param opt.secure boolean 是否安全连接发送  注意:true时http协议下不发送
	 * @param opt.httponly boolean 是否仅允许服务器操作(提高安全性)  注意:true时浏览器js无法操作
	 * @param opt.samesite string|null 限制第三方Cookie  None|Lax|Strict 详见:
	 * 			https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Set-Cookie/SameSite
	 * 			https://www.ruanyifeng.com/blog/2019/09/cookie-samesite.html
	 * @return this
	 */
	setCookie(key, val, opt){
		let v=key+'='+val, exp=opt?.exp??604800;  if(exp) exp+=this.time;
		if(val===null) v += '; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0';
		else if(exp) v+=`; expires=${new Date(exp).toUTCString()}; Max-Age=`+exp;
		v+=(opt?.path?'; path='+opt.path:'')+(opt?.host?'; domain='+opt.host:'');
		if(opt?.secure) v+='; secure';  if(opt?.httponly) v+='; httponly';
		if(opt?.samesite) v+='; samesite='+opt.samesite;
		return this.$res.setHeader('Set-Cookie',v);
	},

	/**
	 * 响应:代理转发
	 * @param url string 转发目标URL http/https
	 * @param opt json|null 选项
	 * 		origin: string 头origin  默认:移除
	 * 		referer: string 头referer 默认:移除
	 * 		host: string 头host  默认:移除
	 * @return Promise
	 */
	proxy(url, opt=null){
		const H = this.$res.headers;
		for(const k of ['origin','referer','host'])
			if(opt?.[k]) H[k]=opt[k]; else delete H[k];
		const OPT = {method:this.$req.method,headers:H};
		const m='node:'+url.split(':')[0].toLowerCase();
		return new Promise((Y,N)=>{
			let E=e=>N($.err('$.srvHttp.proxy',e,true));
			const $p=require(m).request(url,OPT,pxy=>{
				const {statusCode, headers} = pxy;
				this.$res.writeHead(statusCode,headers);
				pxy.on('error',E).on('end',Y);
				pxy.pipe(this.$res);
			});
			$p.on('error',E);  this.$req.pipe($p);
		})
	},
	/**
	 * 响应:json
	 * @config $.SRV string|null 服务器信息 `{ENV}.{SID}`
	 * @param res mixed|null 响应数据
	 * 		非null+非object:转为{data}
	 * @param err object|null 异常信息
	 * @return Promise
	 */
	resJson(res=null, err=null){
		if(!$.isObj(res,false)) res=res===null?{}:{data:res};
		if(!err) res._ekey=res._ekey??''; else{
			err = $.err('$.srvHttp.resJson', err, true);
			if(err.get('sys')){ // 系统异常
				res._ekey = '$.srvHttp.500';
				res._emsg=this.msg(res._ekey);
			}
			else{ // 外部异常
				const {key,val}=err.get(); res._ekey=key;
				res._emsg=this.msg(key);  res._args=val;
			}
		}
		let LOG=null;  res._time=$.date(null,'t');
		if(!$.DEBUG) res=JSON.stringify(res); else{
			if($.SRV) res.__srv=$.SRV;
			if(err){
				const E = err ? $.mix({},err.get()) : null;
				if(E.key!=='$.srvHttp.404') res.__err=E;
				else res.__uri = this.$route; // 路由异常
				if(E.sys) delete E.val; else delete E.msg;
				if($._proc){
					if(E.msg) E.msg = $._proc(E.msg, this);
					if(E.fix) E.fix = $._proc(E.fix, this);
				}
				E.fix=E.fix.trim().split('\n').map(v=>v.trim());
			}
			LOG = JSON.stringify(res, null, 4);
			const log = this.log(null, 'full');
			res.__log = $._proc ? $._proc(log,this) : log;
			res = JSON.stringify(res, null, 4);
		}
		if(this.$opt._logRes) this.log('response: '+(LOG||res));
		err&&this.log(err);  return this.resRaw(res,200,'json');
	},
	/**
	 * 响应:html
	 * 		异常回调渲染, 未配置则内置简单的响应输出
	 * @param $ctr object 控制器  渲染函数的this
	 * 		注意: 异常(err!=null)时: $ctr可能为空
	 * @param data json|null 渲染数据  null->{}
	 * 		异常时追加data.$err: $.err()实例
	 * @param err object|null 异常信息
	 * @return Promise
	 */
	resHtml($ctr, data=null, err=null){
		if(!err){
			const fn=this.$opt._htmMap[this.$route.ctr];
			const htm = fn.call($ctr, data||{});
			return this.resRaw(htm, 200, 'html');
		}
		const htmErr=this.$opt._htmErr;  data=data||{};
		err=data.$err=$.err('$.srvHttp.resHtml',err,true);
		if(htmErr) data = htmErr.call($ctr, data);
		else{
			let {sys,key,val}=err.get(), ekey, emsg;
			if(sys) emsg=this.msg(ekey='$.srvHttp.500');
			else emsg = this.msg(ekey=key, val);
			const x = ' content="width=device-width,'
				+'initial-scale=1.0,user-scalable=0"';
			data = '<meta name="viewport"'+x+'>\n'
				+`<title>ERROR</title>\n<h2>${emsg}</h2>`;
			data+='\n<p style="color:#888;">'+ekey+'</p>';
			if($.DEBUG){
				const dts = $.mix({},err.get());
				dts.log = this.log(null, 'full');
				if(sys) delete dts.val; else delete dts.msg;
				$._proc && ['msg','log','fix'].forEach(k=>{
					if(dts[k]) dts[k]=$._proc(dts[k],this);
				});
				dts.fix=dts.fix.trim().split('\n').map(v=>v.trim());
				const v=$.enc('\n'+JSON.stringify(dts,null,4)+'\n');
				data += '\n\n<pre id="__srv_debug__">'+v+'</pre>';
			}
		}
		this.log(err);  return this.resRaw(data,200,'html');
	},
	/**
	 * 响应:file  TODO:暂不支持断点/分片
	 * @param file string 文件的完整路径
	 * @param opt json|null 选项
	 * 		mime: boolean 是否设置mime  默认:true
	 * 			this.setMime(file的后缀名, '*')
	 * 		cache: int|boolean 缓存控制  默认:true
	 * 			int: [强缓存]有效期(秒) 含协商缓存
	 * 				客户端后续不请求,直接取缓存
	 *			true: [协商缓存]对比头信息(this)
	 *				响应头: Last-Modified / ETag
	 *			false: 不缓存
	 * @param fail function|null 异常回调
	 * 		null ? res404('file') : fail(err)
	 * @return Promise
	 */
	async resFile(file, opt=null, fail=null){
		const {mime,cache}=$.mix({mime:true,cache:true},opt);
		if(mime) this.setMime(file.split('.').pop(), '*');
		// 缓存控制
		if(!cache) this.setHeader('Cache-Control','no-store');
		else{
			// 强缓存
			if(typeof cache==='number')
				this.setHeader('Cache-Control','max-age='+cache);
			// 协商缓存
			const v = await $.fsStat(file, {ign:true,big:true});
			if(!v) return this.res404('file'); // 文件不存在
			const L = new Date((v.mtimeMs+'')*1).toUTCString();
			let T = $.radix(v.mtimeNs,10,64)+'-';
			T+=$.radix(v.size,10,64)+'-'+$.radix(v.ino,10,64);
			const HL = this.header('if_modified_since');
			const HE = this.header('if_none_match');
			if(HL===L && HE===T) return this.resRaw('',304);
			this.setHeader({'Last-Modified':L, ETag:T});
		}
		// 响应内容
		if(!fail) fail = ()=>this.res404('file');
		const $s=require('node:fs').createReadStream(file);
		$s.on('error',fail).pipe(this.$res);
		return new Promise(Y=>$s.on('end',Y));
	},
	/**
	 * 响应:404
	 * @param ekey string 路由异常KEY
	 * 		path, file, call, ...
	 * @param code int 响应状态码
	 * @param type string|null 类型 json|html
	 * 		null: 根据请求类型  GET?html:json
	 * @return Promise
	 */
	res404(ekey, code=200, type=null){
		type = type||(this.way==='GET'?'html':'json');
		const uri=this.$route;  if(uri) uri.err=ekey;
		const err=$.err('@$.srvHttp.404',{ekey},true);
		return type==='json' ? this.resJson(null,err)
			: this.resHtml(null,null,err);
	},
	/**
	 * 响应:500
	 * @param err mixed 异常信息 内部:$.err()
	 * @param code int 响应状态码  仅系统异常
	 * @param type string|null 类型 json|html
	 * 		null: 根据请求类型  GET?html:json
	 * @return Promise
	 */
	res500(err, code=200, type=null){
		err = $.err('$.srvHttp.res500', err, true);
		type = type||(this.way==='GET'?'html':'json');
		if(err.get('sys')) this.$res.statusCode=code;
		return type==='json' ? this.resJson(null,err)
			: this.resHtml(null,null,err);
	},
	/**
	 * 响应:原始数据
	 * @param str string|Buffer|Unit8Array 响应数据
	 * @param code int 响应状态码
	 * @param ext string|null 后缀 null:不设或已设
	 * 		无前缀'.',  this.setMime(ext, 'html')
	 * @return Promise
	 */
	resRaw(str, code=200, ext=null){
		this.$res.statusCode = code;
		if(ext) this.setMime(ext, 'html');
		return new Promise(Y=>this.$res.end(str,Y));
	},
});
// 默认语言消息
$._lang('$.srvHttp',{
	zh_cn:{
		404:'路径未找到',500:'通信错误,请稍后再试.',
		600:'暂不支持POST的multipart/form-data',
	},
});
